package org.hackystat.project;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClientException;
import org.hackystat.dailyprojectdata.resource.filemetric.jaxb.FileMetricDailyProjectData;
import org.hackystat.project.explorer.ProjectExplorer;
import org.hackystat.projecttrajectory.ProjectTrajectoryProperties;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectIndex;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.time.period.Day;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Creates ProjectSummaries for the user. The user is determined by the
 * ProjectTrajectoryProperties.
 * 
 * @author Cam Moore
 * 
 */
public final class PersonalProjectSummaryFactory {
  private SensorBaseClient sbClient;
  private DailyProjectDataClient dpdClient;
  private ProjectTrajectoryProperties properties;
  private Logger logger;
  private static PersonalProjectSummaryFactory instance = null;

  /**
   * Constructs a PersonalProjectSummaryFactory.
   * 
   * @throws ProjectException
   *             if there is a problem with the properties, sensorbase or
   *             DailyProjectData clients.
   */
  private PersonalProjectSummaryFactory() throws ProjectException {
    logger = HackystatLogger.getLogger("org.hackystat.project.PersonalProjecSummaryFactory");
    properties = ProjectTrajectoryProperties.getInstance();
    sbClient = new SensorBaseClient(properties.getSensorbase(), properties.getEmail(), properties
        .getPassword());
    try {
      sbClient.authenticate();
    }
    catch (SensorBaseClientException e) {
      logger.warning("Could not authenticate Sensorbase " + properties.getSensorbase());
      throw new ProjectException("Could not authenticate Sensorbase " + properties.getSensorbase(),
          e);
    }
    dpdClient = new DailyProjectDataClient(properties.getDailyProjectData(), properties.getEmail(),
        properties.getPassword());
    try {
      dpdClient.authenticate();
    }
    catch (DailyProjectDataClientException e) {
      logger.warning("Could not authenticate DailyProjectData " + properties.getDailyProjectData());
      throw new ProjectException("Could not authenticate DailyProjectData "
          + properties.getDailyProjectData(), e);
    }
  }

  /**
   * @return The singleton PersonalProjectSummaryFactory.
   * @throws ProjectException
   *             If there is a problem constructing the singleton instance.
   */
  public static synchronized PersonalProjectSummaryFactory getInstance() throws ProjectException {
    if (instance == null) {
      instance = new PersonalProjectSummaryFactory();
    }
    return instance;
  }

  /**
   * @return A list of the user's defined projects.
   */
  public List<Project> getProjects() {
    ArrayList<Project> projects = new ArrayList<Project>();
    try {
      ProjectIndex index = sbClient.getProjectIndex(properties.getEmail());
      for (ProjectRef ref : index.getProjectRef()) {
        projects.add(sbClient.getProject(ref));
      }
    }
    catch (SensorBaseClientException e) {
      logger.warning(e.getMessage());
    }
    return projects;
  }

  /**
   * @param project
   *            a Project, the project to summarize.
   * @return The ProjectSummayr for today.
   */
  public ProjectSummary getLatestSummary(Project project) {
    ProjectSummary summary = null;
    XMLGregorianCalendar now = Tstamp.makeTimestamp(Day.getInstance());
    if (Tstamp.lessThan(project.getStartTime(), now)) {
      if (Tstamp.lessThan(now, project.getEndTime())) {
        summary = getProjectSummary(project, now);
      }
      else {
        summary = getProjectSummary(project, project.getEndTime());
      }
    }
    return summary;
  }

  /**
   * @param project
   *            a Project.
   * @return A list of the ProjectSummaries for each active day of the given
   *         project.
   */
  public List<ProjectSummary> getSummaries(Project project) {
    return getSummaries(project, false);
  }

  /**
   * @param project
   *            a Project.
   * @param verbose
   *            a boolean, if true outputs progress.
   * @return A list of the ProjectSummaries for each active day of the given
   *         Project.
   */
  public List<ProjectSummary> getSummaries(Project project, boolean verbose) {
    ArrayList<ProjectSummary> summaries = new ArrayList<ProjectSummary>();
    // get the current summary
    ProjectSummary summary = getProjectSummary(project, Tstamp.makeTimestamp(Day.getInstance()));
    Vector<DailyEffort> activeDays = summary.getActiveDayVector();
    if (verbose) {
      System.out.println(project.getName() + " has " + activeDays.size() + " days to summarize.");
    }
    // we need to build a summary for each active day.
    DailyEffort activeDay;
    long effortTotal = 0;
    long dayTotal = 0;
    for (Iterator<DailyEffort> iter = activeDays.iterator(); iter.hasNext();) {
      activeDay = iter.next();
      if (verbose) {
        System.out.println("Summarizing " + activeDay.getDay());
      }
      effortTotal += activeDay.getEffort(); // we already have the effort for
      // each active day
      dayTotal++;
      XMLGregorianCalendar tstamp = Tstamp.makeTimestamp(activeDay.getDay());
      ProjectSummary dailySummary = new ProjectSummary(tstamp, project.getName(), getProjectSize(
          project, tstamp), effortTotal, dayTotal);
      summaries.add(dailySummary);
    }
    return summaries;
  }

  /**
   * Command line test interface.
   * 
   * @param args
   *            a String array.
   */
  public static void main(String[] args) {
    try {
      PersonalProjectSummaryFactory factory = PersonalProjectSummaryFactory.getInstance();
      ProjectExplorer explorer = new ProjectExplorer();
      List<Project> definedProjects = factory.getProjects();
      System.out.println("There are " + definedProjects.size() + " projects defined.");
      Project project;
      for (Iterator<Project> iter = definedProjects.iterator(); iter.hasNext();) {
        project = iter.next();
        System.out.println(explorer.toDetailedDescription(project));
        if (project.getName().equals("Default")) {
          System.out.println("Processing Default");
          List<ProjectSummary> allSummaries = factory.getSummaries(project, true);
          System.out.println("There are " + allSummaries.size() + " project summaries.");
        }
      }
    }
    catch (ProjectException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   * Returns the timestamp of the first Day that sensordata was collected for
   * the given project.
   * 
   * @param project
   *            a Project.
   * @return The XMLGregorianCalendar representing the time of the first sensor
   *         data entry.
   * @throws SensorBaseClientException
   *             if there is a problem with the sensorbase client.
   */
  private XMLGregorianCalendar startDate(Project project) throws SensorBaseClientException {
    // find first day of data
    SensorDataIndex index = sbClient.getProjectSensorData(properties.getEmail(), project.getName());
    List<SensorDataRef> data = index.getSensorDataRef();
    Day day;
    if (data.isEmpty()) {
      day = Day.getInstance(); // return today
    }
    else {
      SensorData firstEntry = sbClient.getSensorData(data.get(0));
      XMLGregorianCalendar dataTime = firstEntry.getTimestamp();
      // now convert to uniform starting time
      day = Day.getInstance(dataTime.getYear(), dataTime.getMonth() - 1, dataTime.getDay());
    }
    return Tstamp.makeTimestamp(day);
  }

  /**
   * @param project
   *            a Project.
   * @param date
   *            an XMLGregorianCalendar, the date to calculate the summary.
   * @return The ProjectSummary for the given project and date.
   */
  private ProjectSummary getProjectSummary(Project project, XMLGregorianCalendar date) {
    ProjectSummary summary = new ProjectSummary(date, project.getName(), 0, 0, 0);
    fillSummary(project, date, summary);
    return summary;
  }

  /**
   * Fills the given ProjectSummary.
   * 
   * @param project
   *            a Project.
   * @param date
   *            a XMLGregorianCalendar the date.
   * @param summary
   *            a ProjectSummary.
   */
  private void fillSummary(Project project, XMLGregorianCalendar date, ProjectSummary summary) {
    String user = properties.getEmail();
    try {
      summary.setTotalLines(getProjectSize(project, date));
      long totalEffort = 0;
      // start on first day with sensor data
      XMLGregorianCalendar currentTime = startDate(project);
      if (Tstamp.greaterThan(currentTime, date)) {
        totalEffort = 0; // project hasn't started yet.
        summary.setTotalEffort(0);
      }
      else {
        long dailyEffort;
        Day activeDay;
        while (Tstamp.greaterThan(date, currentTime)) {
          dailyEffort = dpdClient.getDevTime(user, project.getName(), currentTime)
              .getTotalDevTime().longValue();
          totalEffort += dailyEffort;
          if (dailyEffort > 0) {
            activeDay = Day.getInstance(currentTime.getYear(), currentTime.getMonth() - 1,
                currentTime.getDay());
            summary.addActiveDay(new DailyEffort(activeDay, dailyEffort));
          }
          // increment the date
          currentTime = Tstamp.incrementDays(currentTime, 1);
        }
        // get last day's data
        dailyEffort = dpdClient.getDevTime(user, project.getName(), currentTime).getTotalDevTime()
            .longValue();
        totalEffort += dailyEffort;
        if (dailyEffort > 0) {
          activeDay = Day.getInstance(currentTime.getYear(), currentTime.getMonth() - 1,
              currentTime.getDay());
          summary.addActiveDay(new DailyEffort(activeDay, dailyEffort));
        }

        summary.setTotalEffort(totalEffort);
      }
    }
    catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (SensorBaseClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   * @param project
   *            a Project.
   * @param date
   *            a XMLGregorianCalendar, the date.
   * @return The size of the project on the given date.
   */
  private long getProjectSize(Project project, XMLGregorianCalendar date) {
    long size = 0;
    String user = properties.getEmail();
    // find first day of data
    XMLGregorianCalendar dataStartTime;
    try {
      dataStartTime = startDate(project);
      // get total lines
      FileMetricDailyProjectData fileData = dpdClient.getFileMetric(user, project.getName(), date,
          "TotalLines");
      if (fileData.getTotal() > 0) {
        size = Math.round(fileData.getTotal());
      }
      else {
        // look backwards by day until we find a size or we get to the start day
        // of the project.
        XMLGregorianCalendar currentTime = date;
        while (size == 0 && Tstamp.greaterThan(currentTime, dataStartTime)) {
          // decrement currentTime by one day
          currentTime = Tstamp.incrementDays(currentTime, -1);
          size = Math.round(dpdClient.getFileMetric(user, project.getName(), currentTime,
              "TotalLines").getTotal());
        }
      }
    }
    catch (SensorBaseClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    return size;
  }
}
